feat: add normalizePluginMetadata() for map→array and alias resolution#686
feat: add normalizePluginMetadata() for map→array and alias resolution#686
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…collection utilities Adds a normalizePluginMetadata() function that converts map-formatted plugin metadata to arrays, resolves field aliases (triggers → hooks), and recursively normalizes nested plugins. This eliminates the need for downstream projects (e.g. studio) to patch this normalization themselves. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds normalizePluginMetadata() to handle map-to-array conversion and legacy field name resolution for plugin metadata, solving a patching issue in downstream projects like studio. The runtime currently only handles dual-format support (map/array) for top-level stack definitions via normalizeStackInput(), but plugins need similar normalization for their own metadata collections.
Changes:
- Adds
normalizePluginMetadata()function to convert map-formatted metadata to arrays, resolve field aliases (e.g.,triggers→hooks), and recursively normalize nested plugins - Adds
METADATA_ALIASESconstant to map legacy field names to canonical names - Exports both utilities from
@objectstack/specroot andSharednamespace - Includes 13 comprehensive tests covering conversion, aliasing, merging, recursion, and edge cases
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
packages/spec/src/shared/metadata-collection.zod.ts |
Implements METADATA_ALIASES constant and normalizePluginMetadata() function with three-step normalization: alias resolution with merging, map→array conversion, and recursive nested plugin handling |
packages/spec/src/shared/metadata-collection.test.ts |
Adds comprehensive test suite with 13 tests covering map→array conversion, alias resolution (triggers→hooks), recursive normalization, and edge cases |
packages/spec/src/index.ts |
Exports normalizePluginMetadata and METADATA_ALIASES from root package (also available via Shared namespace) |
| // 1. Resolve aliases (e.g. triggers → hooks), merging with any existing canonical values | ||
| for (const [alias, canonical] of Object.entries(METADATA_ALIASES)) { | ||
| if (alias in result) { | ||
| const aliasValue = normalizeMetadataCollection(result[alias]); | ||
| const canonicalValue = normalizeMetadataCollection(result[canonical]); | ||
|
|
||
| // Merge: canonical array wins; alias values are appended | ||
| if (Array.isArray(aliasValue)) { | ||
| (result as Record<string, unknown>)[canonical] = Array.isArray(canonicalValue) | ||
| ? [...canonicalValue, ...aliasValue] | ||
| : aliasValue; | ||
| } | ||
|
|
||
| delete (result as Record<string, unknown>)[alias]; | ||
| } | ||
| } | ||
|
|
||
| // 2. Normalize map-formatted collections → arrays | ||
| for (const field of MAP_SUPPORTED_FIELDS) { | ||
| if (field in result) { | ||
| (result as Record<string, unknown>)[field] = normalizeMetadataCollection(result[field]); | ||
| } | ||
| } |
There was a problem hiding this comment.
The current implementation normalizes collections twice in certain cases. When an alias field exists (e.g., triggers), both the alias value (line 223) and the canonical value (line 224) are normalized. Then, all MAP_SUPPORTED_FIELDS including the canonical field are normalized again in step 2 (line 240). While normalizeMetadataCollection() is idempotent so this doesn't cause incorrect results, it does perform unnecessary work.
Consider optimizing by tracking which fields have already been normalized in step 1, or by skipping already-normalized canonical fields in step 2. For example, you could collect canonical field names from METADATA_ALIASES and skip them when iterating through MAP_SUPPORTED_FIELDS.
Downstream projects (e.g. studio) must patch plugin metadata normalization at boot because the runtime only handles dual-format (map/array) for objects, not for actions, workflows, hooks, etc. Plugins commonly define these as maps and use legacy field names like
triggersinstead ofhooks.Changes
normalizePluginMetadata()— new utility that:MAP_SUPPORTED_FIELDSMETADATA_ALIASES(currentlytriggers→hooks), merging into existing canonical valuespluginsMETADATA_ALIASES— extensibleRecord<string, MapSupportedField>for legacy field name mappings@objectstack/specroot andSharednamespaceUsage
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.